package lelouet.datamap;

import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;

import javax.xml.bind.*;
import javax.xml.namespace.QName;

/** Utility class for mainly storing and loading a {@link DataMap} */
public class Utils {

	protected static JAXBContext context = null;

	protected static final Class<ObjectFactory> factoryclass = ObjectFactory.class;

	protected static final synchronized JAXBContext context()
	throws JAXBException {
		if (context == null) {
			context = JAXBContext.newInstance(factoryclass);
		}
		return context;
	}

	/**
	 * @return the first value associated to a server with given name if there
	 *         is one, on null
	 */
	public static Double getValue(DataMap map, String serverName) {
		for (ServerPoint point : map.getServerPoints()) {
			if (point.getServer().equals(serverName)) {
				return point.value;
			}
		}
		return null;
	}

	/**
	 * @return a snapshot of the values associated with given server and its
	 *         positions, or null if there is not such a server.<br />
	 *         This map is not backed to the datamap and can be modified without
	 *         modifying the datamap.<br />
	 *         This function is useful when you want to iterate over the
	 *         positions of a server, for it helps not having to do several
	 *         passes for the same server
	 */
	public static HashMap<ServerPosition, Double> getValues(DataMap map,
			String serverName) {
		HashMap<ServerPosition, Double> ret = null;
		for (final ServerPoint point : map.getServerPoints()) {
			if (point.getServer().equals(serverName)) {
				if (ret == null) {
					ret = new HashMap<ServerPosition, Double>();
				}
				ret.put(point.getPosition(), point.getValue());
			}
		}
		return ret;
	}

	/** describe the elements in a map */
	public static String describeDataMap(DataMap map) {
		StringBuilder ret = new StringBuilder();
		ret.append("map :\n");
		if (!map.getServerPoints().isEmpty()) {
			for (ServerPoint point : map.getServerPoints()) {
				ret.append("  ");
				ret.append(point.getServer());
				ret.append('.');
				ret.append(point.getPosition());
				ret.append(" : ");
				ret.append(point.getValue());
				ret.append("\n");
			}
		}
		if (!map.getServerPoints().isEmpty()) {
			ret.append(" local points :\n");
			for (LocalPoint point : map.getLocalPoints()) {
				ret.append("  ");
				ret.append(point.getPosX());
				ret.append(" : ");
				ret.append(point.getPosY());
				ret.append(" : ");
				ret.append(point.getPosZ());
				ret.append(" : ");
				ret.append(point.getValue());
				ret.append("\n");
			}
		}
		return ret.toString();
	}

	/** load a xml file into a {@link DataMap} object */
	public static DataMap loadDataMap(File file) {
		try {
			Unmarshaller u = context().createUnmarshaller();
			Object o = u.unmarshal(new FileInputStream(file));
			try {
				return (DataMap) o;
			} catch (ClassCastException e) {
				JAXBElement<?> elem = (JAXBElement<?>) o;
				return (DataMap) elem.getValue();
			}
		} catch (Exception e) {
			e.printStackTrace(System.err);
			throw new UnsupportedOperationException("TODO : catch better", e);
		}
	}

	/** save a map as xml in specified filename */
	public static boolean saveDataMap(DataMap map, String fileName) {
		return saveDataMap(map, new File(fileName));
	}

	/** save a data map in a file */
	public static boolean saveDataMap(DataMap map, File file) {
		try {
			Marshaller m = context().createMarshaller();
			m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

			// we need to use a QName because the xjc compiler may not add the
			// @xmlRootElement annotation to HeatMap.class. See
			// http://weblogs.java.net/blog/kohsuke/archive/2006/03/why_does_jaxb_p.html
			m.marshal(new JAXBElement<DataMap>(new QName("", "ns2:DataMap"),
					DataMap.class, map), file);
		} catch (JAXBException e) {
			e.printStackTrace(System.err);
			throw new UnsupportedOperationException("TODO : catch better", e);
		}
		return true;
	}
}
